<?php
declare(strict_types=1);

namespace App\Utils\Other\Payment\Sandpay;

use App\Utils\Other\Payment\Base;
use App\Utils\Http\Request;
use App\Exception\InternalException;
use Hyperf\Collection\Arr;
use Hyperf\Context\Context;
use function Hyperf\Support\make;
use function Hyperf\Translation\__;
use function App\Utils\Common\SandpayRsa\{
    decryptByPrivateKey,
    encryptByPublicKey,
    sign,
    generate,
    loadPk12Cert,
    verify,
    encrypt,
    decrypt,
    loadX509Cert
};

/**
 * 杉德支付应用类
 * @package App\Utils\Other\Payment\Sandpay
 */
abstract Class App extends Base {

    /**
     * 验证数据
     * @param array $params
     * @return array
     */
    public function verify(array $params): array
    {
        verify($params['bizData'], $params['sign'], loadX509Cert($this->getConfig('public_cert_path')));

        return json_decode($params['bizData'], true);
    }

    /**
     * 获取公共参数
     * @param array $params
     * @return array
     * @throws \Exception
     */
    protected function params(array $params = []): array
    {
        $publicData = [
            'accessMid' => $this->getConfig('access_mid'),
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '4.0.0',
            'signType' => 'RSA',
            'encryptType' => 'AES',
            'encryptKey' => '',
            'bizData' => ''
        ];

        logs($params);

        if ($publicData['encryptType'] === 'AES') {
            $aesKey = generate(16);
            $publicData['bizData'] = encrypt($params, $aesKey);
            $publicData['encryptKey'] = encryptByPublicKey(
                $aesKey,
                Context::get('public_key')
            );
        }

        $publicData['sign'] = sign(
            $publicData['bizData'],
            Context::get('private_key')
        );

        return $publicData;
    }

    /**
     * 请求
     * @param array $params
     * @return array
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    protected function request(array $params = []): array
    {
        Context::set('public_key', loadX509Cert($this->getConfig('public_cert_path')));
        Context::set('private_key', loadPk12Cert($this->getConfig('private_cert_path'),
            $this->getConfig('private_cert_password')));
        // TODO: Implement request() method.
        /** @var Request $request */
        $request = make(Request::class);
        $body = $this->params($params);
        $response = $request->post(
            $this->getConfig('url').$this->getConfig('path'),
            $body,
            true
        );

        return $this->response($response, $body);
    }

    /**
     * 响应
     * @param $response
     * @param array $params
     * @return array
     */
    protected function response($response, array $params = []): array
    {
        // TODO: Implement response() method.
        logs($response);
        if (!$response) {
            throw new InternalException(__('request.nobody'));
        }
        $result = json_decode($response, true);
        $jsonError = json_last_error();
        if ($jsonError !== JSON_ERROR_NONE) {
            throw new InternalException(__('request.json_error', [
                'code' => $jsonError,
                'message' => json_last_error_msg()
            ]));
        }

        if (Arr::get($result, 'respCode') === 'fail') {
            throw new InternalException(__('request.error', [
                'code' => $result['respCode'],
                'message' => Arr::get($result, 'respDesc')
            ]));
        }

        if ($params['encryptType'] === 'AES') {
            verify($result['bizData'], $result['sign'], Context::get('public_key'));
            $decryptAesKey = decryptByPrivateKey(
                $result['encryptKey'],
                Context::get('private_key')
            );
            $result['bizData'] = json_decode(decrypt($result['bizData'], $decryptAesKey), true);
        }
        logs($result['bizData']);

        if (Arr::get($result['bizData'], 'resultStatus') === 'fail') {
            throw new InternalException(__('request.error', [
                'code' => Arr::get($result['bizData'], 'errorCode'),
                'message' => Arr::get($result['bizData'], 'errorDesc')
            ]));
        }

        return [$params, $result, $result['bizData']];
    }
}